/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       mesh.cpp
 * Author:     karooolek
 * Created on: 2009-03-14
 *
 **********************************************************************************************************************/

#include "mesh.h"

namespace mGameEngine
{
namespace Graphics
{

Mesh::Mesh() :
    _vertices(0), _groups(0), _bones(0), _lowerLodMesh(NULL)
{
}

Mesh::Mesh(const Vector<Vertex> &vertices, const Vector<Group> &groups, const Vector<Bone> &bones) :
    _vertices(0), _groups(0), _bones(0), _lowerLodMesh(NULL)
{
    setVertices(vertices);
    setGroups(groups);
    setBones(bones);
}

Mesh::~Mesh()
{
}

void Mesh::_updateBoundingSphere()
{
    // find all vertices
    Set<uint16> vis;
    for(int i = 0; i != _groups.size(); ++i)
    {
        Group &group = _groups[i];

        for(uint j = 0; j != group.faces.size(); ++j)
        {
            Face &face = group.faces[j];

            vis.insert(face.vertices[0]);
            vis.insert(face.vertices[1]);
            vis.insert(face.vertices[2]);
        }

        for(uint j = 0; j != group.strips.size(); ++j)
        {
            Strip &strip = group.strips[j];

            vis.insert(strip.vertices.begin(), strip.vertices.end());
        }
    }

    // calculate center
    Vector3 c;
    for(Set<uint16>::ConstIterator i = vis.begin();
        i != vis.end();
        ++i)
    {
        const Vertex &vertex = _vertices[*i];

        c += vertex.position;
    }
    c /= float(vis.size());
    _boundingSphere.setCenter(c);
    
    // find radius
    float maxRadius = 0.0f;    
    for(Set<uint16>::ConstIterator i = vis.begin();
        i != vis.end();
        ++i)
    {
        const Vertex &vertex = _vertices[*i];

        maxRadius = max(length(vertex.position - c), maxRadius);
    }
    _boundingSphere.setRadius(maxRadius);
}

void Mesh::setVertices(const Vector<Vertex> &vertices)
{
    // save vertices
    _vertices = vertices;

    _updateBoundingSphere();
}

bool Mesh::hasGroup(const String &groupName) const
{
    for(Vector<Group>::ConstIterator i = _groups.begin();
        i != _groups.end();
        ++i)
    {
        const Group &group = *i;

        if(group.name == groupName)
        {
            return true;
        }
    }

    return false;
}

const Group &Mesh::getGroup(const String &groupName) const
{
    for(Vector<Group>::ConstIterator i = _groups.begin();
        i != _groups.end();
        ++i)
    {
        const Group &group = *i;

        if(group.name == groupName)
        {
            return group;
        }
    }

    return Group();
}

void Mesh::setGroups(const Vector<Group> & groups)
{
    // save groups
    _groups = groups;

    _updateBoundingSphere();
}

bool Mesh::hasBone(const String &boneName) const
{
    for(Vector<Bone>::ConstIterator i = _bones.begin();
        i != _bones.end();
        ++i)
    {
        const Bone &bone = *i;

        if(bone.name == boneName)
        {
            return true;
        }
    }

    return false;
}

const Bone &Mesh::getBone(const String &boneName) const
{
    for(Vector<Bone>::ConstIterator i = _bones.begin();
        i != _bones.end();
        ++i)
    {
        const Bone &bone = *i;

        if(bone.name == boneName)
        {
            return bone;
        }
    }

    return Bone();
}

void Mesh::setBones(const Vector<Bone> &bones)
{
    // save bones
    _bones = bones;

    // TODO what should we do here
}


void Mesh::calculateNormals()
{
    // clear normals
    for(uint i = 0; i != _vertices.size(); ++i)
    {
        _vertices[i].normal = Vector3();
    }

    // every group
    for(uint i = 0; i != _groups.size(); ++i)
    {
        Group &group = _groups[i];

        // every face
        for(uint j = 0; j != group.faces.size(); ++j)
        {
            Face &face = group.faces[j];

            Vertex &v0 = _vertices[face.vertices[0]];
            Vertex &v1 = _vertices[face.vertices[1]];
            Vertex &v2 = _vertices[face.vertices[2]];

            Vector3 a = v1.position - v0.position;
            Vector3 b = v2.position - v0.position;
            Vector3 normal = cross(a.normalize(), b.normalize());
            v0.normal += normal;
            v1.normal += normal;
            v2.normal += normal;
        }

        // TODO every strip
        for(uint j = 0; j != group.strips.size(); ++j)
        {
            Strip &strip = group.strips[j];

            for(uint k = 0; k <= strip.vertices.size()-4; k += 2)
            {
                Vertex &v0 = _vertices[strip.vertices[k]];
                Vertex &v1 = _vertices[strip.vertices[k+1]];
                Vertex &v2 = _vertices[strip.vertices[k+2]];
                Vertex &v3 = _vertices[strip.vertices[k+3]];

                Vector3 a = v1.position - v0.position;
                Vector3 b = v2.position - v0.position;
                Vector3 normal = cross(a.normalize(), b.normalize());
                v0.normal += normal;
                v1.normal += normal;
                v2.normal += normal;

                a = v2.position - v3.position;
                b = v1.position - v3.position;
                normal = cross(a.normalize(), b.normalize());
                v1.normal += normal;
                v2.normal += normal;
                v3.normal += normal;
            }
        }
    }

    // normalize normals
    for(uint i = 0; i != _vertices.size(); ++i)
    {
        _vertices[i].normal.normalize();
    }
}

void Mesh::calculateTangents()
{
    // clear tangents
    for(uint i = 0; i != _vertices.size(); ++i)
    {
        _vertices[i].tangent = Vector3(0.0f);
    }

    // every group
    for(uint i = 0; i != _groups.size(); ++i)
    {
        Group &group = _groups[i];

        // every face
        for(uint j = 0; j != group.faces.size(); ++j)
        {
            Face &face = group.faces[j];

            Vertex &v0 = _vertices[face.vertices[0]];
            Vertex &v1 = _vertices[face.vertices[1]];
            Vertex &v2 = _vertices[face.vertices[2]];

            Vector3 a = v1.position - v0.position;
            a.normalize();

            v0.tangent += a;
            v1.tangent += a;
            v2.tangent += a;
        }

        // TODO every strip
    }

    // normalize tangents
    for(uint i = 0; i != _vertices.size(); ++i)
    {
        _vertices[i].tangent.normalize();
    }
}

void Mesh::generateLowerLODMeshes(uint8 levels, float vertReduction)
{
    // TODO this still doesn't work, don't use it :P
    // based on article
    // "A simple, fast and Effective polygon reduction algorithm"
    // by Stan Melax
    // http://docs.happycoders.org/orgadoc/graphics/rendering/prog_mesh_and_poly_simpl/gdmag.pdf

    // can't generate
    if(!levels || vertReduction <= 0.0f || vertReduction >= 1.0f)
    {
        return;
    }

    // create new mesh
    _lowerLodMesh = new Mesh(_vertices, _groups, _bones);
    Vector<Vertex> &vertices = _lowerLodMesh->_vertices;
    Vector<Group> &groups = _lowerLodMesh->_groups;
    Vector<Bone> &bones = _lowerLodMesh->_bones;

    // vertices and faces adjacency tables
    Vector<List<uint16> > adjVertices(vertices.size());
    Vector<List<std::pair<uint16, uint16> > > adjFaces(vertices.size());

    for(uint i = 0; i != groups.size(); ++i)
    {
        Group &group = groups[i];

        for(uint j = 0; j != group.faces.size(); ++j)
        {
            Face &face = group.faces[j];

            adjVertices[face.vertices[0]].push_back(face.vertices[1]);
            adjVertices[face.vertices[0]].push_back(face.vertices[2]);
            adjFaces[face.vertices[0]].push_back(std::make_pair(i, j));

            adjVertices[face.vertices[1]].push_back(face.vertices[2]);
            adjVertices[face.vertices[1]].push_back(face.vertices[0]);
            adjFaces[face.vertices[1]].push_back(std::make_pair(i, j));

            adjVertices[face.vertices[2]].push_back(face.vertices[0]);
            adjVertices[face.vertices[2]].push_back(face.vertices[1]);
            adjFaces[face.vertices[2]].push_back(std::make_pair(i, j));
        }
    }

    for(uint i = 0; i != vertices.size(); ++i)
    {
        adjVertices[i].unique();
        adjFaces[i].unique();
    }

    // calc how much vertices to delete
    uint del = uint(vertices.size() * vertReduction);
    while(del--)
    {
        float minCost = 999999999999.9f;
        uint16 minI(0), minJ(0);

        // select edge to collapse
        for(uint i = 0; i != adjVertices.size(); ++i)
        {
            Vertex &u = vertices[i];

            for(List<uint16>::Iterator j = adjVertices[i].begin(); j != adjVertices[i].end(); ++j)
            {
                Vertex &v = vertices[*j];
                
                if(i == *j)
                {
                    continue;
                }

                // uv edge length
                float l = length(v.position - u.position);

                // uv edge max curvature
                float maxC = 0.0f;
                for(List<std::pair<uint16, uint16> >::Iterator k = adjFaces[i].begin(); k != adjFaces[i].end(); ++k)
                {
                    Face &f = groups[k->first].faces[k->second];
                    
                    Vector3 fn = (vertices[f.vertices[0]].normal + vertices[f.vertices[1]].normal + vertices[f.vertices[2]].normal).normalize();
                    
                    float minC = 1.0f;
                    for(List<std::pair<uint16, uint16> >::Iterator l = adjFaces[i].begin(); l != adjFaces[i].end(); ++l)
                    {
                        if(k == l) continue;

                        Face &n = groups[l->first].faces[l->second];

                        if(n.vertices[0] != *j && n.vertices[1] != *j && n.vertices[2] != *j) continue;

                        Vector3 nn = (vertices[n.vertices[0]].normal + vertices[n.vertices[1]].normal + vertices[n.vertices[2]].normal).normalize();

                        minC = min(minC, 0.5f * (1.0f - dot(fn, nn)));
                    }

                    maxC = max(minC, maxC);
                }

                // calculate cost
                float cost = l * maxC;

                if(cost < minCost)
                {
                    minCost = cost;
                    minI = i;
                    minJ = *j;

                    // cant find smaller cost, lets break!
                    if(minCost <= 0.0f)
                    {
                        break;
                    }
                }

            }
        }
        
        if(minCost > 0.2f)
        {
            return;
        }

//        printf("removing\t%d\tminCost  = %f\tminI = %d\tminJ = %d\n", del, minCost, minI, minJ);

        List<std::pair<uint16, uint16> > tmpFaces = adjFaces[minI];
        for(List<std::pair<uint16, uint16> >::Iterator k = tmpFaces.begin(); k != tmpFaces.end(); ++k)
        {
            Face &f = groups[k->first].faces[k->second];

            // remove triangle
            if(f.vertices[0] == minJ || f.vertices[1] == minJ || f.vertices[2] == minJ)
            {
                //printf("delete face containig UV\n");
                adjFaces[f.vertices[0]].remove(std::make_pair(k->first, k->second));
                adjFaces[f.vertices[1]].remove(std::make_pair(k->first, k->second));
                adjFaces[f.vertices[2]].remove(std::make_pair(k->first, k->second));

                f.vertices[0] = f.vertices[1] = f.vertices[2] = 0;
                //std::swap(groups[k->first].faces[k->second], groups[k->first].faces.back());
                //groups[k->first].faces[k->second] = groups[k->first].faces.back();
                //groups[k->first].faces.pop_back();
            }
            // replace U with V
            else
            {
                //printf("replace U with V\n");
                if(f.vertices[0] == minI)
                {
                    //adjVertices[f.vertices[0]].remove(minI);
                    //adjVertices[f.vertices[0]].push_back(minJ);
                    //adjVertices[f.vertices[0]].unique();
                    f.vertices[0] = minJ;
                }
                else if(f.vertices[1] == minI)
                {
                    //adjVertices[f.vertices[1]].remove(minI);
                    //adjVertices[f.vertices[1]].push_back(minJ);
                    //adjVertices[f.vertices[1]].unique();
                    f.vertices[1] = minJ;
                }
                else if(f.vertices[2] == minI)
                {
                    //adjVertices[f.vertices[2]].remove(minI);
                    //adjVertices[f.vertices[2]].push_back(minJ);
                    //adjVertices[f.vertices[2]].unique();
                    f.vertices[2] = minJ;
                }
            }

            adjVertices[f.vertices[0]].remove(minI);
            adjVertices[f.vertices[1]].remove(minI);
            adjVertices[f.vertices[2]].remove(minI);
            adjVertices[f.vertices[0]].push_back(minJ);
            adjVertices[f.vertices[1]].push_back(minJ);
            adjVertices[f.vertices[2]].push_back(minJ);
            adjVertices[f.vertices[0]].unique();
            adjVertices[f.vertices[1]].unique();
            adjVertices[f.vertices[2]].unique();

        }

        adjVertices[minI].clear();
        adjFaces[minI].clear();

        // TODO select and delete some edge
        // very easy but unimplementable ;P
        // WTF id doeasnt seem to work well
    }

    // generate next levels
    _lowerLodMesh->generateLowerLODMeshes(levels - 1, vertReduction);
}

void Mesh::draw() const
{
    // select LOD
    if(_lowerLodMesh && _lod >= 1.0f)
    {
        _lowerLodMesh->_lod = _lod - 1.0f;
        return _lowerLodMesh->draw();
    }

    // enable arrays
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &_vertices[0].position);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, sizeof(Vertex), &_vertices[0].normal);
    glEnableClientState(GL_SECONDARY_COLOR_ARRAY);
    glSecondaryColorPointer(3, GL_FLOAT, sizeof(Vertex), &_vertices[0].tangent);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &_vertices[0].texCoords);

    // draw each group
    for(uint i = 0; i != _groups.size(); ++i)
    {
        const Group &group = _groups[i];

        // set groups material as active
        if(group.material)
        {
            group.material->set();
        }

        // draw edges
        if(!group.edges.empty())
        {
            glDrawElements(GL_LINES, group.edges.size() * 2, GL_UNSIGNED_SHORT, group.edges[0].vertices);
        }

        // draw faces
        if(!group.faces.empty())
        {
            //    glPolygonMode(GL_FRONT, GL_LINE);
            glDisable(GL_CULL_FACE);
            glDrawElements(GL_TRIANGLES, group.faces.size() * 3, GL_UNSIGNED_SHORT, group.faces[0].vertices);
            //    glPolygonMode(GL_FRONT, GL_FILL);
            glEnable(GL_CULL_FACE);
        }

        // draw strips
        for(uint i = 0; i != group.strips.size(); ++i)
        {
        //    glPolygonMode(GL_FRONT, GL_LINE);
            const Strip &strip = group.strips[i];
            if(strip.vertices.size() >= 3)
            {
                glDrawElements(GL_TRIANGLE_STRIP, strip.vertices.size(), GL_UNSIGNED_SHORT, &(strip.vertices[0]));
            }
        //    glPolygonMode(GL_FRONT, GL_FILL);
        }
    }

    // disable arrays
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}

}
}

